home *** CD-ROM | disk | FTP | other *** search
/ PC Plus SuperCD (UK) 1998 August / PC Plus SuperCD 50a Issue 142 (CD142a) (August 1998).iso / essent / FIXES / CSeries.exe / issue100 / LIFELITE.TXT < prev   
Encoding:
Text File  |  1994-11-01  |  21.5 KB  |  420 lines

  1.                             LIFE LITE PRIMER EDITION
  2.                              WRITTEN BY IAN SHARPE
  3.       ----------------------------------------------------------------------
  4.                                 THE GAME OF LIFE
  5.  
  6.       The Game Of Life was invented by the mathematician John Conway and 
  7.       first reached a wide public when it was written up in Scientific 
  8.       American in 1970 or thereabouts In those days it was mostly played on
  9.       squared paper. Nowadays computers take all the hard work out of this 
  10.       fascinating invention. To some it's nothing more than a toy; to others 
  11.       it and related cellular automata are subjects for serious study.
  12.  
  13.       In this implementation the screen is divided into a grid of cells 40 
  14.       wide by 24 deep. A cell may be live (red) or dead (white). You begin by 
  15.       creating the first generation of live cells, or seed. Use the cursor 
  16.       keys to move the cursor, and the spacebar to toggle a cell between 
  17.       live and dead.
  18.  
  19.       Press [Return], and the program will quickly create successive 
  20.       generations of cells. A new generation is created by scanning every 
  21.       cell and counting up the number of live neighbours. A neighbour is 
  22.       classed as the eight cells that border horizontally, vertically and 
  23.       diagonally.
  24.  
  25.       The cell under scrutiny is live or dead in the next generation 
  26.       according to the following rules:
  27.  
  28.       * None or one live neighbour: Dies of loneliness.
  29.       * Two live neighbours: Stays in the same state as it is now.
  30.       * Three live neighbours: Lives in the next generation.
  31.       * Four or more live neighbours: Dies of suffocation.
  32.  
  33.       When every cell on the screen has had its state assessed and made live 
  34.       or dead according to the rules, the new generation is complete and the 
  35.       process repeats.
  36.  
  37.       Here are some interesting patterns that I found lying in a gutter on 
  38.       the information superhighway. A # represents a live cell, a dot is a
  39.       blank. Where components of a shape are separated by blanks, the exact
  40.       number of blanks is crucial - for example the eight between the
  41.       rightmost block and the rest of the shape in Glider Gun. You need to
  42.       view these in a monospaced font, such as the screen font or Courier.
  43.  
  44.                                ..#.
  45.                                ...#
  46.          ###                   ####
  47.        BLINKER                 GLIDER
  48.  
  49.  
  50.        ##...##
  51.        #.#.#.# 
  52.        #.#.#.#                 #..#.
  53.        ..#.#..                 ....#
  54.        ..#.#..                 #...#
  55.        .##.##.                 .####
  56.        .##.##.
  57.       PULSATOR                 TRAIN
  58.  
  59.  
  60.  
  61.           ....##..
  62.           ..#...#.
  63.       ##  .#.....#        ##
  64.       ##..##.#...#........## 
  65.           .#.....#
  66.           ..#...#.
  67.           ....##..
  68.       SHUTTLE
  69.  
  70.  
  71.  
  72.                  ##
  73.              ....##....#...
  74.              .##     .#####.##..
  75.       ##.....###     #..##....#.
  76.       ##     .##  ...##........#........##
  77.              ....##  ..#.......#        ##
  78.                  ##         ...#
  79.                             ..#.
  80.                             ##..
  81.       GLIDER GUN - it just about fits. Keep it well clear of the edges, and 
  82.       put it slightly towards the lower half of the screen.
  83.  
  84.  
  85.       ----------------------------------------------------------------------
  86.                              HOW THE PROGRAM WORKS
  87.  
  88.       I suggest you print out LIFELITE.CPP (it's about three pages long) so 
  89.       that you can easily read the comments and see the code that's being 
  90.       referred to.
  91.  
  92.       The state of play is stored in the 3D array world[][][]. Conceptually 
  93.       you can think of it as two pages of graph paper. At any one time, one 
  94.       page will hold the existing generation while the new generation is 
  95.       written to the other. The new generation then becomes the existing 
  96.       generation, the next generation is written back to the original page. 
  97.       It's global because several functions need to access it. The array 
  98.       subscripts are world[row][column][page].
  99.  
  100.       A page in the array is larger than the screen area so that it has a 
  101.       border of unused cells around the playing area. This is so that the 
  102.       bit of the program that adds up the number of live cells around the 
  103.       cell currently being examined doesn't have to take special action when 
  104.       considering cells at the edge of the screen. Not having to make 
  105.       special arrangements here makes the programming simpler.
  106.  
  107.       The #define constants are purely to make the program code more 
  108.       readable and easier to alter.
  109.  
  110.       The code itself is broken into six functions:
  111.  
  112.  
  113.       main()
  114.       ------
  115.       Start here, as it gives you an overview of the program's architecture 
  116.       and flow of control. 
  117.  
  118.       Main() spends most of its time in a loop which constantly cycles 
  119.       between two functions: input_seed() in which the user inputs a new 
  120.       seed, and breed() which successively breeds and displays new 
  121.       generations until the user presses one of the two keys that allows him 
  122.       or her to exit this function. Both input_seed() and breed() return a 
  123.       value which reports what the user wants to do. This return value will 
  124.       either trigger the ending of the loop (and therefore the program) or a 
  125.       new loop cycle.
  126.  
  127.       At the end of main(), immediately before the program terminates, it 
  128.       calls tidyup() to restore certain aspects of the display that were 
  129.       changed during program execution. Otherwise the screen would be messy 
  130.       upon return to DOS.
  131.  
  132.  
  133.       input_seed()
  134.       ------------
  135.       Enables the user to input a new starting generation. It first 
  136.       clears all array elements to DEAD, makes the cursor invisible, sets 
  137.       white as the background colour, then wipes the screen.
  138.       
  139.       Next it prints the message at the bottom the screen. It sets the 
  140.       background colour to green, moves the cursor to the bottom left, and 
  141.       clears to the end of the lion, producing a green strip. Then it prints 
  142.       the text.
  143.  
  144.       All these text/background colour functions etc and the #define values 
  145.       used as parameters are detailed in the online help.
  146.  
  147.       After setting the colours to the ones used for printing live (red) and 
  148.       dead (white) cells we initialise variables x and y. These are used to 
  149.       keep track of the input cursor, and the initialisation values put it 
  150.       near the centre of the screen when it first appears. The function 
  151.       movecursor() moves the cursor from the first set of coordinate 
  152.       parameters to the second. Using the same parameters twice allows us to 
  153.       place the cursor on the screen for the first time.
  154.       
  155.       Note that the cursor we're talking about here is not the normal text 
  156.       screen hardware cursor. Thus was made invisible earlier, though it can 
  157.       still be moved around the screen in order to set the position at which 
  158.       the next character will be printed. However, we don't want to use it 
  159.       for cell input because a cell is two characters wide whereas the 
  160.       hardware cursor is only one character wide. It would look odd. 
  161.       Instead, the input cursor is artificially created by using a pair of ≡ 
  162.       characters. These are moved around by the program in response to 
  163.       presses of the cursor keys, and the colour of the cursor characters 
  164.       and their background are changed according to the state of the cell 
  165.       they're sitting on.
  166.  
  167.       Next comes a loop in which cursor and other keypresses are processed. 
  168.       This loop cannot end because the condition, being a literal non-zero 
  169.       number, is forever true. Exit from this function is only via the two 
  170.       return() statements triggered by the [Return] and [Q] keypresses.
  171.  
  172.       The loop executes only one action: a SWITCH statement within which the 
  173.       main business of input_seed() is conducted. Getch() waits for the user 
  174.       to press a key, which is converted to upper case by toupper(), the 
  175.       return value from which causes the SWITCH to run down its list of CASE 
  176.       statements looking for a match:
  177.  
  178.       The '\r' character is the [Return] key. It causes the function to 
  179.       return, passing back a code which main() interprets as the signal to 
  180.       keep looping. Main()'s next action will be to execute the breed() 
  181.       function.
  182.  
  183.       'Q' is the quit key. This causes input_seed() to return a code which 
  184.       main() interprets as the signal to stop its main loop, and thereby end 
  185.       the program.
  186.  
  187.       I use #defines for QUIT and KEEP_GOING, rather than quoting the 
  188.       literal codes, simply to make the program more readable. You can read 
  189.       and understand parts of the program that use these codes without 
  190.       having to find out what some raw numbers mean.
  191.  
  192.       A space - ' ' - toggles the state of the cell under the cursor. We 
  193.       always input a seed on page [0] of the world array. Since x,y on the 
  194.       screen is column, row and the first pair of subscripts to world[] are 
  195.       row, column, x and y are switched round. ^ is a logical operator 
  196.       meaning XOR - eXclusive OR. A useful property of XOR is that any 
  197.       number XOR itself is zero, and any number XOR zero is itself. So:
  198.  
  199.                  1 XOR 1 = 0 and 0 XOR 1 = 1
  200.       
  201.       And since DEAD=0 and LIVE=1,
  202.  
  203.                  DEAD XOR LIVE=LIVE and LIVE XOR LIVE=DEAD
  204.  
  205.       XORing provides an easy way of switching a variable between two values 
  206.       without having to test its present value. ^= follows the C++ 
  207.       convention of shortening: x = x ^ 1 to: x ^= 1.
  208.  
  209.       After changing the state of the current cell in the world[] array, we 
  210.       call up the function showcell which displays the cell at the 
  211.       coordinates specified. This will wipe out the cursor, so we then call 
  212.       movecursor() again to re-display it.
  213.  
  214.       We don't want to fall through the floor into the next CASE, so we 
  215.       break out of the SWITCH block. This will take program execution out 
  216.       into the loop, so the next things to be executed are the WHILE( 1 ) 
  217.       then the SWITCH again.
  218.  
  219.       Case '\0' is triggered when the keypress yields a value of zero. This 
  220.       happens with certain keys, such as the cursor keys. Instead of 
  221.       generating a single character code, they produce two codes. The first 
  222.       one is always zero. After getch() has taken the '\0' from the keyboard 
  223.       buffer, the other code will be left sitting in there, waiting for the 
  224.       next keyboard read to pluck it out. It isn't certain that the next 
  225.       keyboard character will signify a cursor movement - other keys use 
  226.       this zero-plus-code code. However, for the moment we assume that we're 
  227.       going to have to move the cursor after then next getch() call. In 
  228.       preparation for this, we put the present position of the cursor in the 
  229.       variables nx and ny. This is because moving the cursor follows this 
  230.       logic:
  231.           * Overprint the old cursor position with the current state of 
  232.             the cell, thus wiping the cursor characters.
  233.           * Print the cursor characters at the new screen position, 
  234.             adjusting the foreground and background colours according to
  235.             the state of the new cell.
  236.       This requires us to have two sets of coordinates - old cursor position 
  237.       and new cursor position, so we have the position in two sets of 
  238.       variables.
  239.  
  240.       We then use getch() to get the waiting character from the keyboard 
  241.       buffer, and use its value as the basis of another SWITCH block. It is 
  242.       perfectly legal to have nested SWITCHES like this, though this 
  243.       function is getting rather long and for the sake of legibility could 
  244.       be broken into two shorter functions.
  245.  
  246.       The output from this second call to getch() isn't converted to upper 
  247.       case because the codes generated by the cursor keys are them same 
  248.       whether or not [Shift] or [Caps Lock] is pressed.
  249.  
  250.       The CASE statements use #define values for legibility again. You can 
  251.       see the underlying character values at the head of the program. Each 
  252.       CASE adjusts a cursor ordinate. To avoid repetitious typing, we only 
  253.       test that the new value is legal (ie it hasn't stepped off the screen) 
  254.       when all the possible program flow routes through this section of code 
  255.       have converged below this inner SWITCH block.
  256.  
  257.       We could have used a lot of IFing and ELSEing to test the cursor 
  258.       position but the (test) ? <true result> : <false result>; operator is 
  259.       far neater. If the new cursor ordinate is still on the screen, its 
  260.       present value is overwritten with its present value - no change, in 
  261.       other words. If the cursor would be off-screen, the value in nx or ny 
  262.       is replaced with the old value, pulling it back to where it was.
  263.  
  264.       The program doesn't now need to worry about whether or not the cursor 
  265.       actually moves to a new cell. It just calls up movecursor() with the 
  266.       old and new values of the cursor coordinates, and it doesn't matter if 
  267.       all we're doing is re-printing the cursor on top of itself. Similarly, 
  268.       when we update the variables x and y with the present position of the 
  269.       cursor, it doesn't matter if there was no movement. Occasionally doing 
  270.       a bit of unnecessary work in a situation where execution speed isn't 
  271.       an issue makes the program simpler to understand, and actually causes 
  272.       it to execute fewer instructions in the more common situation of 
  273.       having the cursor away from the edge.
  274.  
  275.       The closing brace following the update of the variables is the bottom 
  276.       of the outer SWITCH, so we're taken back to the top of the loop to 
  277.       wait for a new keypress.
  278.  
  279.  
  280.       breed()
  281.       -------
  282.       This function successively breeds and displays new generations until 
  283.       the user presses [Return] or [Q].
  284.  
  285.       At the start of the function we use a procedure similar to the one at 
  286.       the head of input_seed() to print a help bar at the bottom of the 
  287.       screen. We don't clear the screen or the array, though, because the 
  288.       newly-input first generation is breed()'s starting position.
  289.  
  290.       Remember that the world[] array is conceptually two pages, and that 
  291.       the program reads one page (the old generation) while writing to the 
  292.       other (the new generation). It then swaps round so that the new 
  293.       generation becomes the old one, and the previous old generation is 
  294.       overwritten with its grandchildren.
  295.  
  296.       The constant flipping of perception of which pages constitute old and 
  297.       new generations is achieved by using variables oldgen and newgen as the 
  298.       third subscript to the world[] array. In one cycle oldgen=0 and 
  299.       newgen=1. In the next cycle oldgen=1 and newgen=0. Before starting the 
  300.       loop which controls the breeding cycle we initialise these values. We 
  301.       also initialise a variable named healthy. In the body of the loop the 
  302.       value of healthy acts as a monitor on the state of health of the 
  303.       latest generation. A zero value indicates that there are no live 
  304.       cells. This triggers the printing of a message. It's more than a bit 
  305.       of fun - it keeps inattentive users feeling comfortable because they 
  306.       know the program hasn't crashed and that the programmer does pay 
  307.       attention to detail. It's a good approach to adopt, but keep it subtle 
  308.       and effective. Otherwise your programs will feel fussy and over-
  309.       elaborate (and therefore stressful to use). They will also contain 
  310.       extra bugs because you spent so much time making sure everybody's kept 
  311.       fully aware of how clever you are, rather than concentrating on core 
  312.       functionality (trebly stressful, and a double own-goal).
  313.       
  314.       Breed()'s main loop is another never-ending cycle that only terminates 
  315.       when you force the function to return following an appropriate 
  316.       keypress.
  317.  
  318.       The breeding is done by a nested loop which continues until you press 
  319.       a key. If you look below this loop you will see a SWITCH block which 
  320.       reads and processes the keypress. If the pressed key wasn't one of two 
  321.       listed in the CASE statements, program execution loops back up to the 
  322.       WHILE( 1 ) causing the breeding cycle to resume. Notice that in 
  323.       contrast to the main loop in input_seed(), we need braces here. The 
  324.       inner WHILE and the SWITCH are two actions for the main loop to 
  325.       execute.
  326.  
  327.       Looking at the inner WHILE now, the first thing it does is check the 
  328.       value of the variable healthy. Volatile local variables start life 
  329.       with a random value - they inherit whatever garbage was sitting in the 
  330.       bytes that are newly allocated to them. This is why we earlier 
  331.       initialised healthy with a non-zero value - had we not done so, and had 
  332.       healthy accidentally already contained zero, the actions after this IF 
  333.       test would have been executed, even if it was inappropriate for the 
  334.       message to be printed. On subsequent loop cycles, if healthy does 
  335.       contain zero, the message is printed. To avoid it flickering as it is 
  336.       repeatedly overwritten with dead cells, the CONTINUE statement forces 
  337.       execution to jump to the head of the loop. This skips all the stuff 
  338.       that follows, curtailing operations to a keytest/print message cycle.
  339.  
  340.       Healthy is nothing more than a running total of the sums of live 
  341.       neighbours calculated for each cell. It therefore has to be set to 
  342.       zero before we start calculating. A completely dead playing field will 
  343.       leave it at that value.
  344.  
  345.       The uses a pair of nested loops to scan every cell in turn. It only 
  346.       makes calculations for visible cells - the ones in the off-screen 
  347.       border (remember that we made world[] bigger than the screen) are not 
  348.       included in the scan. However, when calculating the sum of live 
  349.       neighbours for cells at the edge of the screen, the cells in the border 
  350.       are part of the sum. This makes the programming easy, but means that 
  351.       the edges of the world artificially stunt growth. A better approach 
  352.       would be to have the world wrap round, so that top and bottom edges 
  353.       are joined, as are left and right edges. Cells that move off one edge 
  354.       then appear on the opposite edge. This wouldn't be hard to arrange - 
  355.       don't delay, enrol today as a valued founder subscriber to the Life Pro
  356.       upgrade savings plan!
  357.  
  358.       The variable sum is just a total of the values in neighbouring cells. 
  359.       It is permissible to split complicated lines in this way because the ; 
  360.       marks the end of the expression, not a carriage return.
  361.  
  362.       Having summed up the neighbours, we use a SWITCH block to decide the 
  363.       state of the cell in the next generation. This is a straight 
  364.       application of the rules of the game, following which we call 
  365.       showcell() to display the state of the new cell. Healthy is updated.
  366.      
  367.       When the loop pair has run its course, the XOR technique described
  368.       earlier is used to flip the values of oldgen and newgen.
  369.  
  370.       Calculating and displaying in the same loop gives maximum overall 
  371.       program execution speed. Speed could be sacrificed to give a cleaner 
  372.       display update. All the calculations would be done in one pair of 
  373.       nested loops, and the update in a separate pair. The screen update 
  374.       part of cycle would be quicker, because it doesn't have to wait for a 
  375.       cell to be calculated before it is printed.
  376.       
  377.       This might be worthwhile - the program runs too quickly on a fast PC 
  378.       anyway, so on most PCs we could afford to lose some speed. An 
  379.       associated upgrade would be to introduce a mechanism to automatically 
  380.       limit the speed on faster PCs, so that on machines faster than, say, a 
  381.       25MHz 486, it didn't go any quicker. Life Pro Gold?
  382.  
  383.       Program execution has reached the bottom of the inner WHILE loop, and
  384.       jumps back to the top. If the user pressed a key recently, it is analysed
  385.       as mentioned earlier.
  386.  
  387.  
  388.       showcell()
  389.       ----------
  390.       Displays the current state of a cell on the screen. It converts 
  391.       world[] subscript coordinates to screen coordinates, moves the 
  392.       hardware cursor to that place, and prints either a pair of block 
  393.       characters to make a filled square, or a pair of blanks, depending on 
  394.       the state of the cell. The main point about the translation of 
  395.       coordinates is that each cell in word[] occupies two horizontally 
  396.       adjacent text cells on the screen, so we have to double the X 
  397.       ordinate.
  398.  
  399.  
  400.       movecursor()
  401.       ------------
  402.       Moves the artificial input_seed() cursor from one place on the screen 
  403.       to another. A pair of ≡ characters is used to mark the cursor 
  404.       position, and the colours of background and foreground are chosen to 
  405.       reflect the state of the cell the cursor is sitting on.
  406.  
  407.  
  408.       tidyup()
  409.       --------
  410.       Called up immediately before program termination to tidy up the 
  411.       display. To avoid a nastily coloured DOS prompt messily overwriting
  412.       the latest generation, the colours are changed to sensible values, the
  413.       hardware cursor restored to visibility, and the screen cleared. Ideally,
  414.       during initialisation the program would have saved the shape of the
  415.       cursor and the current text and background colours, and restored those.
  416.  
  417.  
  418.                                     .oOo.
  419.  
  420.